//
//  YCBaseAPIManager.swift
//  YCNetworking
//
//  Created by YC on 2017/3/6.
//  Copyright © 2017年 YC. All rights reserved. mail:yangchong364@qq.com
//

import UIKit


let kYCAPIBaseManagerRequestID = "kYCAPIBaseManagerRequestID"

public enum YCAPIManagerRequestType{
    case get
    case post
    case put
    case delete
}

public enum YCAPIManagerErrorType{
    case none
    case success
    case noContent
    case paramsError
    case timeout
    case noNetWork
}


/****************************************************************************************/
/*                                  YCAPIManager                                        */
/****************************************************************************************/
public protocol YCAPIManager: NSObjectProtocol {
    //Required
    var methodName: String { get }
    var serviceType: String { get }
    var requestType: YCAPIManagerRequestType { get }
    var shouldCache: Bool { get }
    
    //Optional
    var shouldLoadFromNative: Bool { get }
    
    func cleanData()
    func reformParams(_ params: Dictionary<String,Any>) -> Dictionary<String,Any>
    func loadDataWithParams(_ params: Dictionary<String,Any>) -> Int
}

public extension YCAPIManager {
    var shouldLoadFromNative: Bool { return false}
    
    func cleanData() {}
    func reformParams(_ params: Dictionary<String,Any>) -> Dictionary<String,Any>{ return params }
    func loadDataWithParams(_ params: Dictionary<String,Any>) -> Int { return 0 }
}

/****************************************************************************************/
/*                        YCAPIManagerParamSourceDelegate                               */
/****************************************************************************************/
//让manager能够获取调用API所需要的数据
public protocol YCAPIManagerParamSource: class {
    func paramsForApi(_ manager: YCBaseAPIManager) -> Dictionary<String,Any>
}



/****************************************************************************************/
/*                         YCAPIManagerCallBackDelegate                                 */
/****************************************************************************************/
public protocol YCAPIManagerCallBackDelegate: class {
    func callAPIDidSuccess(in manager: YCBaseAPIManager)
    func callAPIDidFailed(in manager: YCBaseAPIManager)
}



/****************************************************************************************/
/*                             YCAPIManagerValidator                                    */
/****************************************************************************************/
public protocol YCAPIManagerValidator: class {
    func manager(_ manager: YCBaseAPIManager, isCorrectWithCallBack data: Any) -> Bool
    func manager(_ manager: YCBaseAPIManager, isCorrectWithParams data: Dictionary<String,Any>) -> Bool
}



/****************************************************************************************/
/*                            YCAPIManagerInterceptor                                   */
/****************************************************************************************/
public protocol YCAPIManagerInterceptor: class {
    func manager(_ manager: YCBaseAPIManager, beforePerformSuccessWith response: YCURLResponse) -> Bool
    func manager(_ manager: YCBaseAPIManager, afterPerformSuccessWith response: YCURLResponse)
    
    func manager(_ manager: YCBaseAPIManager, beforePerformFailWith response: YCURLResponse) -> Bool
    func manager(_ manager: YCBaseAPIManager, afterPerformFailWith response: YCURLResponse)
    
    func manager(_ manager: YCBaseAPIManager, shouldCallAPIWith params:Dictionary<String,Any>) -> Bool
    func manager(_ manager: YCBaseAPIManager, afterCallingAPIWith params:Dictionary<String,Any>)
}



/****************************************************************************************/
/*                           YCAPIManagerDataReformer                                   */
/****************************************************************************************/
public protocol YCAPIManagerDataReformer: class {
    func manager(_ manager: YCBaseAPIManager, reformData: Any) -> Any
}

open class YCBaseAPIManager: NSObject {
    public weak var child: YCAPIManager!
    public weak var paramSource: YCAPIManagerParamSource!
    public weak var delegate: YCAPIManagerCallBackDelegate?
    public weak var validator: YCAPIManagerValidator!
    public weak var interceptor: YCAPIManagerInterceptor?
    
    var response: YCURLResponse!
    
    private(set) var errorMessage = ""
    private(set) var errorType = YCAPIManagerErrorType.none
    
    private var isReachable: Bool {
        get {
            return YCAppContext.shared.isReachable
        }
    }
    
    private var _isLoading: Bool = false
    var isLoading: Bool {
        set {
            self._isLoading = newValue
        }
        get {
            if self.requestIDList.count == 0 {
                return false
            }else {
                return self._isLoading
            }
        }
    }
    
    private var fetchedRawData: Any!
    private var requestIDList = [Int]()
    private var isNativeDataEmpty = false
    private var cache = YCCache.shared
    
    
    public override init() {

        super.init()
        
        if self is YCAPIManager {
            self.child = self as! YCAPIManager
        }else {
            NSException(name: NSExceptionName(rawValue: "YCBaseAPIManager初始化错误!"), reason: "子类没有实现YCAPIManager协议", userInfo: nil).raise()
        }

    }
    
    deinit {
        self.cancelAllRequests()
        self.requestIDList.removeAll()
    }
    
    //MARK: - public methods
    public func cancelAllRequests() {
        YCApiProxy.shared.cancelRequest(with: self.requestIDList)
        self.requestIDList.removeAll()
    }
    
    public func cancelRequest(with requestID: Int) {
        YCApiProxy.shared.cancelRequest(with: requestID)
        self .removeRequestID(with: requestID)
    }
    
    public func fetchData(with reformer: YCAPIManagerDataReformer?) -> Any! {
        
        if reformer != nil {
            return reformer!.manager(self, reformData: self.fetchedRawData)
        }else {
            return self.fetchedRawData
        }
        
    }
    
    //MARK: - calling api
    public func loadData() -> Int {
        let params = self.paramSource.paramsForApi(self)
        let requestID = self.loadData(with: params)

        return requestID;
    }
    
    public func loadData(with params: Dictionary<String,Any>) -> Int {
        var requestID = 0
        
        let apiParams = self.reformParams(params)
        
        if self.shouldCallAPI(with: apiParams) {
            if self.validator.manager(self, isCorrectWithParams: apiParams) {
                if self.child.shouldLoadFromNative {
                    self.loadDataFromNative()
                }
                
                // 先检查一下是否有缓存
                if self.child.shouldCache && self.hasCache(with: apiParams) {
                    return requestID
                }
                
                if self.isReachable {
                    self.isLoading = true
                    
                    let apiProxy = YCApiProxy.shared
                    
                    switch self.child.requestType {
                    case .get:
                        requestID = apiProxy.callGet(with: apiParams, serviceIdentifier: self.child.serviceType, methodName: self.child.methodName, success: { [unowned self] (response) in
                            self.successedOnCallingAPI(response: response)
                        }, fail: { [unowned self] (response) in
                            self.failedOnCallingAPI(response: response, errorType: .none)
                        })
                    case .post:
                        requestID = apiProxy.callPost(with: apiParams, serviceIdentifier: self.child.serviceType, methodName: self.child.methodName, success: { [unowned self] (response) in
                            self.successedOnCallingAPI(response: response)
                            }, fail: { [unowned self] (response) in
                                self.failedOnCallingAPI(response: response, errorType: .none)
                        })
                    case .put:
                        requestID = apiProxy.callPut(with: apiParams, serviceIdentifier: self.child.serviceType, methodName: self.child.methodName, success: { [unowned self] (response) in
                            self.successedOnCallingAPI(response: response)
                            }, fail: { [unowned self] (response) in
                                self.failedOnCallingAPI(response: response, errorType: .none)
                        })
                    case .delete:
                        requestID = apiProxy.callDelete(with: apiParams, serviceIdentifier: self.child.serviceType, methodName: self.child.methodName, success: { [unowned self] (response) in
                            self.successedOnCallingAPI(response: response)
                            }, fail: { [unowned self] (response) in
                                self.failedOnCallingAPI(response: response, errorType: .none)
                        })
                    }
                }
                
                var params = apiParams
                params[kYCAPIBaseManagerRequestID] = requestID
                
                self.afterCallingAPI(with: params)
                
            }else {
                self.failedOnCallingAPI(response: nil, errorType: .noNetWork)
            }
        }else {
            self.failedOnCallingAPI(response: nil, errorType: .paramsError)
        }
        
        return requestID
    }
    
    //MARK: - api callbacks
    private func successedOnCallingAPI(response: YCURLResponse) {
        self.isLoading = false
        self.response = response
        
        if self.child.shouldLoadFromNative {
            if response.isCache == false {
                UserDefaults.standard.set(response.responseData, forKey: self.child.methodName)
                UserDefaults.standard.synchronize()
            }
        }
        
        if response.content != nil {
            self.fetchedRawData = response.content
        }else {
            self.fetchedRawData = response.responseData
        }
        
        self.removeRequestID(with: response.requestId)
        
        if self.validator!.manager(self, isCorrectWithCallBack: response.content) {
            if self.child.shouldCache && !response.isCache {
                
            }
            
            if self.beforePerformSuccess(with: response) {
                if self.child.shouldLoadFromNative {
                    if response.isCache == true {
                        self.delegate?.callAPIDidSuccess(in: self)
                    }
                    if self.isNativeDataEmpty {
                        self.delegate?.callAPIDidSuccess(in: self)
                    }
                }else {
                    self.delegate?.callAPIDidSuccess(in: self)
                }
            }
            self.afterPerformSuccess(with: response)
        }else {
            self.failedOnCallingAPI(response: response, errorType: .noContent)
        }
        
    }
    
    private func failedOnCallingAPI(response: YCURLResponse!, errorType: YCAPIManagerErrorType) {
        self.isLoading = false
        self.response = response
        
        //统一错误处理
        
//        if response.content["id"] == nil {
//            
//        }else {
//            self.errorType = errorType
//            self.removeRequestID(with: response.requestId)
//            
//            if self.beforePerformFail(with: response) {
//                self.delegate?.callAPIDidFailed(in: self)
//            }
//            
//            self.afterPerformFail(with: response)
//        }
        
            self.errorType = errorType
            self.removeRequestID(with: response.requestId)

            if self.beforePerformFail(with: response) {
                self.delegate?.callAPIDidFailed(in: self)
            }

            self.afterPerformFail(with: response)
        
    }
    
    //MARK: - method for interceptor
    /*
     拦截器的功能可以由子类通过继承实现，也可以由其它对象实现,两种做法可以共存
     当两种情况共存的时候，子类重载的方法一定要调用一下super
     然后它们的调用顺序是BaseManager会先调用子类重载的实现，再调用外部interceptor的实现
     
     notes:
     正常情况下，拦截器是通过代理的方式实现的，因此可以不需要以下这些代码
     但是为了将来拓展方便，如果在调用拦截器之前manager又希望自己能够先做一些事情，所以这些方法还是需要能够被继承重载的
     所有重载的方法，都要调用一下super,这样才能保证外部interceptor能够被调到
     这就是decorate pattern
     */
    
    func beforePerformSuccess(with response: YCURLResponse) -> Bool {
        var result = true
        
        self.errorType = .success
        
        if self.isEqual(self.interceptor) == false
            && self.interceptor != nil {
            result = self.interceptor!.manager(self, beforePerformSuccessWith: response)
        }
        
        return result
    }
    
    func afterPerformSuccess(with response: YCURLResponse) {
        if self.isEqual(self.interceptor) == false
            && self.interceptor != nil {
            self.interceptor!.manager(self, afterPerformSuccessWith: response)
        }
    }
    
    func beforePerformFail(with response: YCURLResponse) -> Bool {
        var result = true
        
        self.errorType = .success
        
        if self.isEqual(self.interceptor) == false
            && self.interceptor != nil {
            result = self.interceptor!.manager(self, beforePerformFailWith: response)
        }
        
        return result
    }
    
    func afterPerformFail(with response: YCURLResponse) {
        if self.isEqual(self.interceptor) == false
            && self.interceptor != nil {
            self.interceptor!.manager(self, afterPerformFailWith: response)
        }
    }
    
    //只有返回true才会继续调用API
    func shouldCallAPI(with params: Dictionary<String,Any>) -> Bool {
        if self.isEqual(self.interceptor) == false
            && self.interceptor != nil {
            return self.interceptor!.manager(self, shouldCallAPIWith: params)
        }else {
            return true
        }
    }
    
    func afterCallingAPI(with params: Dictionary<String,Any>) {
        if self.isEqual(self.interceptor) == false
            && self.interceptor != nil {
            self.interceptor!.manager(self, afterCallingAPIWith: params)
        }
    }
    
    //MARK: - method for child
    
    func cleanData() {
        self.cache.clean()
        self.fetchedRawData = nil
        self.errorMessage = ""
        self.errorType = .none
    }
    
    func reformParams(_ params: Dictionary<String,Any>) -> Dictionary<String,Any> {
        let childIMP = (self.child as! NSObject).method(for: #selector(self.reformParams(_:)))
        let selfIMP = self.method(for: #selector(self.reformParams(_:)))
        
        if childIMP == selfIMP {
            return params
        }else {
            //child 如果没有实现方法 在extension里默认会返回原来的值
            return self.child.reformParams(params)
        }
        
    }
    
    //MARK: - private methods
    private func removeRequestID(with requestID: Int) {
        var requestIDToRemove: Int?
        
        for storedRequestId in self.requestIDList.enumerated() {
            if storedRequestId.element == requestID {
                requestIDToRemove = storedRequestId.offset
            }
        }
        
        if requestIDToRemove != nil {
            self.requestIDList.remove(at: requestIDToRemove!)
        }
        
    }
    
    private func hasCache(with params: Dictionary<String,Any>) -> Bool {
        let result = self.cache.fetchCachedData(with: self.child.serviceType, methodName: self.child.methodName, requestParams: params)
        
        if result == nil {
            return false
        }
        
        DispatchQueue.main.async { [unowned self] in
            let respone = YCURLResponse(with: result!)
            self.successedOnCallingAPI(response: respone)
        }
        
        return true
    }
    
    private func loadDataFromNative() {
        let result = UserDefaults.standard.object(forKey: self.child.methodName)
        
        if result != nil {
            self.isNativeDataEmpty = false
            
            DispatchQueue.main.async { [unowned self] in
                let data = try! JSONSerialization.data(withJSONObject: result!, options: .prettyPrinted)
                let response = YCURLResponse(with: data)
                
                self.successedOnCallingAPI(response: response)
            }
            
        }else {
            self.isNativeDataEmpty = true
        }
    }
}
